package gozero

import (
	"errors"
	"reflect"
)

const (
	TAG_COLUMN      = "column"
	TAG_PREFIX      = "prefix"
	TAG_PRIMARY_KEY = "primaryKey"
	TAG_FOREIGN_KEY = "foreignKey"
)

type ResultItem map[string]interface{}

func makeResultItem(size int) ResultItem {
	if size == 0 {
		size = 23
	}
	return make(map[string]interface{}, size)
}

type ResultSet []ResultItem

func makeResultSet(size int) ResultSet {
	if size == 0 {
		size = 53
	}
	return make([]ResultItem, 0, size)
}

const (
	ptr = 1 << iota
	embed
	slice
)

type Model struct {
	p        *Model        // 父级指针
	v        reflect.Value // 级联对象的反射值
	t        reflect.Type  // 对象反射类型
	prefix   string        // 字段前缀
	name     string        // 结构体名称
	columns  []string      // 索引字段，包含所有子节点的字段名
	category uint          // 标识v的类型 1指针 2包含 4数组或切片
	fields   []*Field      // 字段
	sub      []*Model      // 子对象

}

type Field struct {
	p            *Model       // 所属结构体的指针
	t            reflect.Type // 字段类型(记录解指针后的类型)
	name         string       // 字段名
	colName      string       // 数据库列映射名称
	isPtr        bool         // 是否为指针类型
	isPrimaryKey bool         // 是否为主键(标注为主键的字段会被合并)
}

func (m *Model) appendField(field *Field) {
	m.columns = append(m.columns, field.colName)
	if m.p != nil {
		m.p.appendField(field)
	}
	if field.p == m {
		m.fields = append(m.fields, field)
	}
}

func (m *Model) appendSubmodel(sub *Model) {
	if sub.p == m {
		m.sub = append(m.sub, sub)
	}
}

func (m *Model) detectCycle(name, prefix string) bool {
	if m.name == name && m.prefix == prefix {
		return false
	}
	if m.p != nil {
		if !m.p.detectCycle(name, prefix) {
			return false
		}
	}
	return true
}

func newModel(entity interface{}) (*Model, error) {
	v := reflect.ValueOf(entity)
	if v.Kind() != reflect.Ptr {
		return nil, errors.New("只能接受指针类型")
	}
	v = v.Elem()
	switch v.Kind() {
	case reflect.Struct:
		m := Model{p: nil, v: v, t: v.Type(), name: v.Type().Name()}
		m.tree(v)
		return &m, nil
	case reflect.Array, reflect.Slice:
		itemValue := reflect.New(v.Type().Elem()).Elem()
		m := Model{p: nil, v: v, t: v.Type().Elem(), name: itemValue.Type().Name(), category: slice}
		m.tree(itemValue)
		return &m, nil
	default:
		return nil, errors.New("只能使用结构体或结构体数组")
	}
}

func (m *Model) tree(v reflect.Value) error {
	if v.Kind() != reflect.Struct {
		return errors.New("入参错误")
	}
	for i := 0; i < v.NumField(); i++ {
		fieldValue := v.Field(i)
		structField := v.Type().Field(i)
		switch fieldValue.Kind() {
		case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
			reflect.Float32, reflect.Float64, reflect.String:
			{
				colName := tag(structField.Tag, TAG_COLUMN, snakecase(structField.Name))
				isPK := isPrimaryKey(structField.Tag)
				field := Field{p: m, name: structField.Name, colName: colName, t: fieldValue.Type(), isPrimaryKey: isPK}
				m.appendField(&field)
			}
		case reflect.Struct:
			{
				prefix := tag(structField.Tag, TAG_PREFIX, "")
				if m.detectCycle(structField.Name, prefix) {
					subModel := Model{p: m, name: structField.Name, t: fieldValue.Type(), prefix: prefix, category: embed}
					subModel.tree(fieldValue)
					m.appendSubmodel(&subModel)
				}
			}
		case reflect.Array, reflect.Slice:
			{
				// 切片类型只能对应结构体类型
				itemType := fieldValue.Type().Elem()
				if itemType.Kind() != reflect.Struct {
					return errors.New("类型不被支持")
				}
				prefix := tag(structField.Tag, TAG_PREFIX, "")
				itemValue := reflect.New(itemType).Elem()
				if m.detectCycle(structField.Name, prefix) {
					subModel := Model{p: m, name: structField.Name, category: slice, t: fieldValue.Type(), prefix: prefix}
					subModel.tree(itemValue)
					m.appendSubmodel(&subModel)
				}
			}
		case reflect.Ptr:
			{
				elemType := fieldValue.Type().Elem()
				colName := tag(structField.Tag, TAG_COLUMN, snakecase(structField.Name))
				switch elemType.Kind() {
				case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
					reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
					reflect.Float32, reflect.Float64, reflect.String:
					field := Field{p: m, name: structField.Name, colName: colName, isPtr: true, t: elemType}
					m.appendField(&field)
				case reflect.Struct:
					prefix := tag(structField.Tag, TAG_PREFIX, "")
					if m.detectCycle(structField.Name, prefix) {
						subModel := Model{p: m, name: structField.Name, category: ptr, t: fieldValue.Type(), prefix: prefix}
						subModel.tree(reflect.New(elemType).Elem())
						m.appendSubmodel(&subModel)
					}
				default:
					return errors.New("类型不被支持")
				}
			}
		default:
			return errors.New("类型不被支持")
		}
	}
	return nil
}

func (m *Model) substep(ri ResultItem) {
	modelValue, isAdd := m.getReflectValue(ri)
	numSetting := 0
	for _, field := range m.fields {
		val, ok := ri[m.prefix+field.colName]
		if !ok {
			continue
		}
		delete(ri, m.prefix+field.colName)
		if !reflect.ValueOf(val).CanConvert(field.t) {
			continue
		}

		modelField := modelValue.FieldByName(field.name)
		modelFieldValue := reflect.ValueOf(val).Convert(field.t)
		if field.isPtr {
			modelField.Set(reflect.New(field.t))
			modelField = modelField.Elem()
		}
		modelField.Set(modelFieldValue)
		numSetting++
	}
	// 如果结构体没有任何字段被填充则不会继续递归
	if numSetting > 0 {
		for i := range m.sub {
			subModel := m.sub[i]
			subModel.v = modelValue.FieldByName(subModel.name)
			subModel.substep(ri)
		}
		if m.category == slice && isAdd {
			m.v.Set(reflect.Append(m.v, modelValue))
		}
		if m.category == ptr {
			m.v.Set(reflect.New(m.v.Type().Elem()))
			m.v.Elem().Set(modelValue)
		}
	}
}

func (m *Model) install(rs ResultSet) {
	for _, item := range rs {
		m.substep(item)
	}
}

func (m *Model) getReflectValue(data ResultItem) (reflect.Value, bool) {
	if m.category == slice {
		for i := 0; i < m.v.Len(); i++ {
			itemValue := m.v.Index(i)
			num := 0
			for _, field := range m.fields {
				if !field.isPrimaryKey {
					continue
				}
				num++
				val, ok := data[m.prefix+field.colName]
				if ok {
					if reflect.ValueOf(val).Convert(field.t).Interface() == itemValue.FieldByName(field.name).Interface() {
						num++
					}
				}
			}
			if num > 0 && num%2 == 0 {
				return itemValue, false
			}
		}
		return reflect.New(m.v.Type().Elem()).Elem(), true
	} else if m.category == ptr {
		return reflect.New(m.v.Type().Elem()).Elem(), false
	} else {
		return m.v, false
	}
}
